/**
 * Copyright 2008 Adam Ruggles.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package kiff.util.transaction;

import java.lang.reflect.Method;

import kiff.util.Provider;
import kiff.util.transaction.annotation.Transactional;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;

/**
 * Test Transactional Interceptor using JPA.
 * @author Adam
 * @version $Id: TransactionInterceptor.java 62 2008-10-31 04:51:50Z a.ruggles $
 * 
 * Created on Sep 30, 2008 at 6:53:43 PM 
 */
public class TransactionInterceptor implements MethodInterceptor {
	/**
     * The <code>Logger</code> is used by the application to generate a log messages.
     */
    private final Logger logger = LoggerFactory.getLogger(TransactionInterceptor.class);

	/**
	 * The Transaction.
	 */
	private Provider<Transaction> provider;

	/**
	 * {@inheritDoc}
	 * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
	 */
	public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
		Transaction tx = provider.get();
		logger.debug("Invoking Method [{}]", methodInvocation.getMethod().getName());
		if (tx.isActive()) {
			logger.debug("Transaction is active.");
			return methodInvocation.proceed();
		}
		try {
			logger.debug("Starting the transaction.");
			tx.begin();
			Transactional transactional = resolveAnnotation(methodInvocation);
			// Set the transaction timeout.
			if (transactional != null && transactional.timeout() > 0) {
				logger.debug("LoaderSetting the transaction timeout to [{}].", transactional.timeout());
				tx.setTransactionTimeout(transactional.timeout());
			}
			if ((transactional != null && transactional.rolebackOnly())) {
				logger.debug("This transaction is being set to rollback.");
				tx.setRollbackOnly();
			}
			// Call the intercepted method.
			Object value = methodInvocation.proceed();
			if (tx.isActive()) {
				if (tx.getRollbackOnly()) {
					logger.debug("Rolling back the transaction.");
					tx.rollback();
				} else {
					logger.debug("Committing the transaction.");
					tx.commit();
				}
			}
			return value;
		} catch (Throwable throwable) {
			logger.error("Error occured in the transaction", throwable);
			try {
				logger.debug("Attempting to roll back the transaction.");
				tx.rollback();
			} catch (Throwable throwableEx) {
				logger.error("Unable to roll back the transaction.", throwableEx);
			}
			throw throwable;
		} finally {
			logger.debug("Ending the transaction.");
			tx.end();
		}
	}

	/**
	 * Returns the transactional annotation if it exists and null if it does not.
	 * @param methodInvocation The methodInvocation.
	 * @return The transactional annotation if it exists and null if it does not.
	 */
	private Transactional resolveAnnotation(final MethodInvocation methodInvocation) {
		Transactional annotation = null;
		Method method = methodInvocation.getMethod();
		Class<?> targetClass = methodInvocation.getThis().getClass().getSuperclass();
		if (method.isAnnotationPresent(Transactional.class)) {
			annotation = method.getAnnotation(Transactional.class);
		} else if (targetClass.isAnnotationPresent(Transactional.class)) {
			annotation = targetClass.getAnnotation(Transactional.class);
		}
		return annotation;
	}

	/**
	 * Sets provider.
	 * @param provider the provider to set.
	 */
	@Inject
	public void setProvider(final Provider<Transaction> provider) {
		this.provider = provider;
	}
}
